package cn.xzc.job.util;

import cn.xzc.job.constant.Constant;
import cn.xzc.job.pojo.PackageScanParam;
import cn.xzc.job.throwable.UtilException;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * @author xzc
 * @Classname ReflectUtil
 * @Description 反射相关
 * @Date 2024/2/17 22:36
 */
public class ReflectUtil {
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ReflectUtil.class);

    @SuppressWarnings("deprecation")
    public static <T extends AccessibleObject> T setAccessible(T accessibleObject) {
        if (null != accessibleObject && !accessibleObject.isAccessible()) {
            accessibleObject.setAccessible(true);
        }
        return accessibleObject;
    }

    /**
     * 将throwable中的checkException转换成runtimeException
     *
     * @param ex 异常
     */
    public static void rethrowRuntimeException(Throwable ex) {
        if (ex instanceof RuntimeException) {
            throw (RuntimeException) ex;
        }
        if (ex instanceof Error) {
            throw (Error) ex;
        }
        throw new UndeclaredThrowableException(ex);
    }

    /**
     * 在package范围获取包含对应注解的方法
     *
     * @param packageName packageName 标准样例：org.example
     * @param annotation  注解
     * @return 包含注解的方法
     */
    public static Set<Method> getMethodsAnnotatedWith(String packageName, Class<? extends Annotation> annotation) {
        //获取包含对应注释的方法
        //断言判断：类中方法上是否存在该注解
        Predicate<Method> methodPredicate = method -> ClassUtil.containAnnotation(method, annotation);
        return transformMethod(scan(packageName, null, methodPredicate));
    }

    /**
     * 在package范围中根据过滤条件查找符合条件的类
     *
     * @param packageName     package范围 标准样例：org.example 当packageName为空时，会扫描调用该方法的项目的target
     * @param classPredicate  class过滤条件
     * @param methodPredicate method过滤条件
     * @return 符合条件的类集合
     */
    public static Set<AnnotatedElement> scan(String packageName, Predicate<Class<?>> classPredicate, Predicate<Method> methodPredicate) {
        //允许包名为空
        //当包名为空时,扫描所有的类class
        PackageScanParam scanParam = getPackageScanParam(packageName, classPredicate, methodPredicate);
        //1.获取ClassLoader
        ClassLoader classLoader = ClassUtil.getClassLoader();
        //2.获取resourceName,从类加载器查找对应路径的所有URL对象
        String resourceName = packagePath(scanParam.getPackageName());
        Enumeration<URL> resources;
        try {
            resources = classLoader.getResources(resourceName);
        } catch (IOException e) {
            log.error(Constant.EXCEPTION_INFO, ThrowableUtil.getThrowableMessage(e));
            throw new UtilException(e);
        }
        //扫描类加载器对应的package下的文件
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            //URL协议类型
            switch (resource.getProtocol()) {
                case "file":
                    scanParam.setCurDirPath(null);
                    scanParam.setJar(null);
                    try {
                        scanParam.setFile(new File(URLDecoder.decode(resource.getFile(), Constant.UTF_8)));
                        //筛选符合条件的file中的类
                        scanFile(scanParam);
                    } catch (IOException e) {
                        log.error(Constant.EXCEPTION_INFO, ThrowableUtil.getThrowableMessage(e));
                        throw new UtilException(e);
                    }
                    break;
                case "jar":
                    scanParam.setFile(null);
                    scanParam.setCurDirPath(null);
                    try {
                        JarURLConnection urlConnection = (JarURLConnection) resource.openConnection();
                        scanParam.setJar(urlConnection.getJarFile());
                        //筛选符合条件中的jar包中的类
                        scanJar(scanParam);
                    } catch (IOException e) {
                        log.error(Constant.EXCEPTION_INFO, ThrowableUtil.getThrowableMessage(e));
                        throw new UtilException(e);
                    }
                    break;
            }
        }
        if (CollectionUtils.isEmpty(scanParam.getRes())) {
            log.info("当前jar包没有扫描到数据，扫描其他相关联的jar包!!!");
            scanParam.setInitialize(false);
            scanJavaClassPaths(scanParam);
        }

        return Collections.unmodifiableSet(scanParam.getRes());
    }

    /**
     * 将AnnotatedElement转换成真实的对应反射操作方法
     *
     * @param annotatedElements 操作结果
     * @return AnnotatedElement转换Class
     */
    private static Set<Class<?>> transformClass(Set<AnnotatedElement> annotatedElements) {
        Set<Class<?>> res = Collections.emptySet();
        if (CollectionUtils.isNotEmpty(annotatedElements)) {
            res = annotatedElements.stream().filter(element -> element instanceof Class)
                    .map(element -> (Class<?>) element)
                    .collect(Collectors.toSet());
        }
        return CollectionUtils.isEmpty(res) ? Collections.emptySet() : Collections.unmodifiableSet(res);
    }

    /**
     * 将AnnotatedElement转换成真实的对应反射操作方法
     *
     * @param annotatedElements 操作结果
     * @return AnnotatedElement转换Method
     */
    private static Set<Method> transformMethod(Set<AnnotatedElement> annotatedElements) {
        Set<Method> res = Collections.emptySet();
        if (CollectionUtils.isNotEmpty(annotatedElements)) {
            res = annotatedElements.stream().filter(element -> element instanceof Method)
                    .map(element -> (Method) element)
                    .collect(Collectors.toSet());
        }
        return CollectionUtils.isEmpty(res) ? Collections.emptySet() : Collections.unmodifiableSet(res);
    }

    /**
     * 根据查询条件转换成package扫描时需要的参数对象
     *
     * @param packageName     packageName
     * @param classPredicate  类过滤条件
     * @param methodPredicate 方法过滤条件
     * @return package扫描时需要的参数对象
     */
    private static PackageScanParam getPackageScanParam(String packageName, Predicate<Class<?>> classPredicate, Predicate<Method> methodPredicate) {
        PackageScanParam scanParam = new PackageScanParam();
        if (StringUtils.isBlank(packageName)) {
            packageName = Constant.EMPTY;
        }
        scanParam.setPackageName(packageName);
        scanParam.setClassPredicate(classPredicate);
        scanParam.setMethodPredicate(methodPredicate);
        scanParam.setRes(new LinkedHashSet<>());
        scanParam.setInitialize(StringUtils.isNotBlank(packageName));
        return scanParam;
    }

    /**
     * 扫描其他相关联的jar包路径
     *
     * @param scanParam 扫描参数
     */
    private static void scanJavaClassPaths(PackageScanParam scanParam) {
        //相关联的所有jar包路径
        final String[] javaClassPaths = ClassUtil.getJavaClassPaths();
        for (String classPath : javaClassPaths) {
            scanParam.setCurDirPath(null);
            scanParam.setJar(null);
            try {
                classPath = URLDecoder.decode(classPath, Constant.UTF_8);
                if (StringUtils.isBlank(classPath)) {
                    continue;
                }
                scanParam.setFile(new File(classPath));
                scanFile(scanParam);
            } catch (IOException e) {
                log.error(Constant.EXCEPTION_INFO, ThrowableUtil.getThrowableMessage(e));
                throw new UtilException(e);
            }
        }
    }

    /**
     * 扫描file文件，并将符合条件的file类保存
     *
     * @param scanParam 扫描参数
     */
    private static void scanFile(PackageScanParam scanParam) {
        File file = scanParam.getFile();
        if (Objects.isNull(file) || !file.exists()) {
            log.warn("要扫描的文件不存在!");
            return;
        }
        //文件
        if (file.isFile()) {
            final String fileName = file.getAbsolutePath();
            //当文件是.class文件
            if (fileName.endsWith(Constant.CLASS_EXT)) {
                int startIndex = StringUtils.isBlank(scanParam.getCurDirPath()) ? 0 : scanParam.getCurDirPath().length();
                // 8为classes长度，fileName.length() - 6为".class"的长度
                final String className = fileName.substring(startIndex, fileName.length() - 6).replace(File.separatorChar, Constant.DOT);
                //加入满足条件的类
                filterClass(scanParam, className);
            } else if (fileName.endsWith(Constant.JAR_FILE_EXT)) {
                //当文件是jar包时
                try {
                    PackageScanParam subClassParam = scanParam.copy();
                    subClassParam.setJar(new JarFile(file));
                    scanJar(subClassParam);
                } catch (IOException e) {
                    log.error(Constant.EXCEPTION_INFO, ThrowableUtil.getThrowableMessage(e));
                    throw new UtilException(e);
                }
            }
        } else if (file.isDirectory()) {
            String curDirPath = scanParam.getCurDirPath();
            //目录
            File[] files = file.listFiles();
            if (ArrayUtil.isNotEmpty(files)) {
                for (File curFile : files) {
                    PackageScanParam subClassParam = scanParam.copy();
                    subClassParam.setFile(curFile);
                    //当前文件夹为最上层的目录路径
                    subClassParam.setCurDirPath(StringUtils.isBlank(curDirPath) ? subPathBeforePackage(file, scanParam.getPackageName()) : curDirPath);
                    //递归：子文件
                    scanFile(subClassParam);
                }

            }
        }
    }

    /**
     * 扫描jar包，并将jar包中的类进行保存
     *
     * @param scanParam 扫描过程中的参数
     */
    private static void scanJar(PackageScanParam scanParam) {
        JarFile jarFile = scanParam.getJar();
        if (Objects.isNull(jarFile) || jarFile.size() == 0) {
            log.warn("要扫描的文件不存在!");
            return;
        }
        Enumeration<JarEntry> jarEntities = jarFile.entries();
        String fileName;
        while (jarEntities.hasMoreElements()) {
            JarEntry entry = jarEntities.nextElement();
            if (!entry.isDirectory() && (fileName = entry.getName()).endsWith(Constant.CLASS_EXT)) {
                //去除fileName开通的/
                fileName = StringUtils.isNotBlank(fileName) && fileName.startsWith(Constant.SLASH) ? fileName.substring(1) : fileName;
                final String className = fileName.substring(0, fileName.length() - 6).replace(Constant.SLASH, Constant.PERIOD);
                filterClass(scanParam, className);
            }
        }
    }

    /**
     * 根据类名过滤在对应package下的CLass
     *
     * @param scanParam 扫描过程中需要的参数，这里需要其中的packageName
     * @param className 当前要存储的类名
     */
    private static void filterClass(PackageScanParam scanParam, String className) {
        if (StringUtils.isBlank(className)) {
            return;
        }
        //判断当前类是否属于package
        String packageName = scanParam.getPackageName();
        String packageWithDot = packageName.endsWith(Constant.PERIOD) ? packageName : packageName + Constant.PERIOD;
        if (StringUtils.isBlank(packageName) || packageName.equals(className) || (className.length() > packageWithDot.length() && className.startsWith(packageWithDot))) {
            filterClass(scanParam, ClassUtil.loadClass(className, scanParam.isInitialize(), null));
        }

    }

    /**
     * 根据过滤条件是否可以保存
     *
     * @param scanParam 扫描过程中需要的参数，这里需要其中的过滤条件
     * @param clazz     需要判断是否保存的class
     */
    private static void filterClass(PackageScanParam scanParam, Class<?> clazz) {
        if (Objects.isNull(clazz)) {
            return;
        }
        Predicate<Class<?>> classPredicate = scanParam.getClassPredicate();
        Predicate<Method> methodPredicate = scanParam.getMethodPredicate();
        boolean classFilter;
        try {
            //可能对应类class存在，但是其中的import出现问题，所以这里进行test断言使用Throwable捕获
            classFilter = Objects.isNull(classPredicate) || classPredicate.test(clazz);
        } catch (Throwable e) {
            log.error(Constant.EXCEPTION_INFO, ThrowableUtil.getThrowableMessage(e));
            return;
        }
        //类过滤条件失败
        if (!classFilter) {
            return;
        }

        if (Objects.isNull(methodPredicate)) {
            //直接添加对应的类：类过滤条件已经成功
            scanParam.getRes().add(clazz);
        } else {
            //过滤条件 Method反射方法
            filterMethod(scanParam, clazz);
        }
    }

    /**
     * 过滤符合条件的方法
     *
     * @param scanParam 方法过滤条件
     * @param clazz     成功过滤的类class
     */
    private static void filterMethod(PackageScanParam scanParam, Class<?> clazz) {
        Predicate<Method> methodPredicate = scanParam.getMethodPredicate();
        try {
            Method[] methods = clazz.getDeclaredMethods();
            if (ArrayUtil.isNotEmpty(methods)) {
                for (Method method : methods) {
                    if (methodPredicate.test(method)) {
                        scanParam.getRes().add(method);
                    }
                }
            }
        } catch (Throwable e) {
            log.error(Constant.EXCEPTION_INFO, ThrowableUtil.getThrowableMessage(e));
        }

    }

    /**
     * 截取file路径前面部分路径，到packageDirName为止
     *
     * @param dirFile     file文件
     * @param packageName packageName
     * @return 截取file路径前面部分路径
     */
    private static String subPathBeforePackage(File dirFile, String packageName) {
        String dirPath = dirFile.getAbsolutePath();
        //剪切截止到packageDirName前面部分
        if (StringUtils.isNotBlank(packageName)) {
            String packageDir = packageName.replace(Constant.DOT, File.separatorChar);
            int indexOf = dirPath.lastIndexOf(packageDir);
            if (indexOf > 0) {
                dirPath = dirPath.substring(0, indexOf);
            } else if (indexOf == 0) {
                dirPath = Constant.EMPTY;
            }
        }
        if (!dirPath.endsWith(File.separator)) {
            dirPath += File.separator;
        }
        return dirPath;
    }


    /**
     * 将package中的点转换成斜杠
     *
     * @param packageName package.getName()
     * @return packageName转换后的resourceName【即packagePath，类加载器扫描resource使用】
     */
    private static String packagePath(String packageName) {
        if (StringUtils.isBlank(packageName)) {
            return Constant.EMPTY;
        }
        String resourceName = packageName.replace(Constant.PERIOD, Constant.SLASH);
        resourceName = resourceName.replace("\\", Constant.SLASH);
        if (resourceName.startsWith(Constant.SLASH)) {
            resourceName = resourceName.substring(1);
        }
        return resourceName;
    }
}
